//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.IO;
using System.Threading;
using System.Collections;
using EffiProz.Core.RowIO;
using EffiProz.Core.Lib;
using EffiProz.Core.Store;
using EffiProz.Core.Errors;


namespace EffiProz.Core.Persist
{

    /**
     * Acts as a manager for CACHED table persistence.<p>
     *
     * This contains the top level functionality. Provides file management services
     * and access.<p>
     *
     * Rewritten for 1.8.0 together with Cache.
     *
     * @author fredt@users
     * @version 1.8.0
     * @since 1.7.2
     */
    public class DataFileCache
    {

        protected EfzFileAccess fa;

        // We are using persist.Logger-instance-specific FrameworkLogger
        // because it is Database-instance specific.
        // If add any static level logging, should instantiate a standard,
        // context-agnostic FrameworkLogger for that purpose.
        // flags
        public const int FLAG_ISSHADOWED = 1;
        public const int FLAG_ISSAVED = 2;
        public const int FLAG_ROWINFO = 3;
        public const int FLAG_190 = 4;

        // file format fields
        public const int LONG_EMPTY_SIZE = 4;     // empty space size
        public const int LONG_FREE_POS_POS = 12;    // where iFreePos is saved
        public const int LONG_EMPTY_INDEX_POS = 20;    // empty space index
        public const int FLAGS_POS = 28;
        public const int INITIAL_FREE_POS = 32;

        //
        public DataFileBlockManager freeBlocks;
        private const int initIOBufferSize = 256;

        //
        protected String dataFileName;
        protected String backupFileName;
        protected Database database;

        // this flag is used externally to determine if a backup is required
        public bool fileModified;
        public int cacheFileScale;

        // post openning constant fields
        protected bool cacheReadonly;

        // cache operation mode
        protected bool storeOnInsert;

        //
        protected int cachedRowPadding = 8;
        protected bool _hasRowInfo = false;

        // reusable input / output streams
        protected RowInputInterface rowIn;
        public RowOutputInterface rowOut;

        //
        public long maxDataFileSize;

        //
        bool is180;

        //
        protected Storage dataFile;
        public long fileFreePosition; //TODO volatile
        protected int maxCacheRows;          // number of Rows
        protected long maxCacheBytes;         // number of bytes
        protected int maxFreeBlocks;
        protected Cache cache;

        //
        private RAShadowFile shadowFile;

        //
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
        internal ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
#else
        private class Dummy { };
        internal Object _lock = new Dummy();
#endif

        public DataFileCache(Database db, String baseFileName)
        {

            initParams(db, baseFileName);

            cache = new Cache(this);
        }
        /**
         * initial external parameters are set here.
         */
        protected virtual void initParams(Database database, String baseFileName)
        {

            this.dataFileName = baseFileName + ".data";
            this.backupFileName = baseFileName + ".backup";
            this.database = database;
            fa = database.logger.getFileAccess();
            cacheFileScale = database.logger.getCacheFileScale();
            cachedRowPadding = 8;

            if (cacheFileScale > 8)
            {
                cachedRowPadding = cacheFileScale;
            }

            cacheReadonly = database.logger.propFilesReadOnly;
            maxCacheRows = database.logger.propCacheMaxRows;
            maxCacheBytes = database.logger.propCacheMaxSize;
            maxDataFileSize = (long)int.MaxValue * cacheFileScale;
            maxFreeBlocks = database.logger.propMaxFreeBlocks;
            dataFile = null;
            shadowFile = null;
        }

        /**
         * Opens the *.data file for this cache, setting the variables that
         * allow access to the particular database version of the *.data file.
         */
        public virtual void open(bool rdy)
        {

            fileFreePosition = 0;

            database.logger.logInfoEvent("open start");

            try
            {
                bool isNio = database.logger.propNioDataFile;
                int fileType = isNio ? ScaledRAFile.DATA_FILE_NIO
                                         : ScaledRAFile.DATA_FILE_RAF;



                String cname = database.getURLProperties().getProperty(
                    EffiProzDatabaseProperties.url_storage_class_name);
                String skey = database.getURLProperties().getProperty(
                    EffiProzDatabaseProperties.url_storage_key);

                if (rdy)
                {
                    dataFile = ScaledRAFile.newScaledRAFile(database,
                            dataFileName, rdy, fileType);

                    initBuffers();

                    return;
                }

                bool preexists = false;
                long freesize = 0;

                if (fa.IsStreamElement(dataFileName))
                {
                    preexists = true;
                }

                dataFile = ScaledRAFile.newScaledRAFile(database, dataFileName,
                        rdy, fileType);

                if (preexists)
                {
                    dataFile.seek(FLAGS_POS);

                    int flags = dataFile.readInt();
                    bool isSaved = BitMap.isSet(flags, FLAG_ISSAVED);

                    database.logger.propIncrementBackup = BitMap.isSet(flags,
                            FLAG_ISSHADOWED);
                    is180 = !BitMap.isSet(flags, FLAG_190);

                    if (!isSaved)
                    {
                        bool restored = true;

                        dataFile.close();

                        if (database.logger.propIncrementBackup)
                        {
                            restored = restoreBackupIncremental();

                            if (!restored)
                            {
                                deleteFile();

                                is180 = false;
                            }
                        }
                        else
                        {
                            restoreBackup();
                        }

                        dataFile = ScaledRAFile.newScaledRAFile(database,
                                dataFileName, rdy, fileType);

                        if (!restored)
                        {
                            initNewFile();
                        }
                    }

                    dataFile.seek(LONG_EMPTY_SIZE);

                    freesize = dataFile.readLong();

                    dataFile.seek(LONG_FREE_POS_POS);

                    fileFreePosition = dataFile.readLong();

                    if (fileFreePosition < INITIAL_FREE_POS)
                    {
                        fileFreePosition = INITIAL_FREE_POS;
                    }

                    if (database.logger.propIncrementBackup
                            && fileFreePosition != INITIAL_FREE_POS)
                    {
                        shadowFile = new RAShadowFile(database, dataFile,
                                                      backupFileName,
                                                      fileFreePosition, 1 << 14);
                    }
                }
                else
                {
                    initNewFile();
                }

                initBuffers();

                fileModified = false;
                freeBlocks = new DataFileBlockManager(maxFreeBlocks,
                                                      cacheFileScale, freesize);

                database.logger.logInfoEvent("open end");
            }
            catch (Exception t)
            {
                database.logger.logSevereEvent("open failed", t);
                close(false);

                throw Error.error(t, ErrorCode.FILE_IO_ERROR,
                                  ErrorCode.M_DataFileCache_open, new Object[] {
                t.Message, dataFileName
            });
            }
        }

        void initNewFile()
        {

            fileFreePosition = INITIAL_FREE_POS;

            dataFile.seek(LONG_FREE_POS_POS);
            dataFile.writeLong(INITIAL_FREE_POS);

            // set shadowed flag;
            int flags = 0;

            if (database.logger.propIncrementBackup)
            {
                flags = BitMap.set(flags, FLAG_ISSHADOWED);
            }

            flags = BitMap.set(flags, FLAG_190);

            dataFile.seek(FLAGS_POS);
            dataFile.writeInt(flags);
        }

        public void setIncrementBackup(bool value)
        {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
               _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif


            try
            {
                dataFile.seek(FLAGS_POS);

                int flags = dataFile.readInt();

                if (value)
                {
                    flags = BitMap.set(flags, FLAG_ISSHADOWED);
                }
                else
                {
                    flags = BitMap.unset(flags, FLAG_ISSHADOWED);
                }

                dataFile.seek(FLAGS_POS);
                dataFile.writeInt(flags);
                dataFile.synch();
            }
            catch (Exception t)
            {
                database.logger.logSevereEvent("backupFile failed", t);
            }
            finally
            {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
               _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        /**
         * Restores a compressed backup or the .data file.
         */
        private bool restoreBackup()
        {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            // in case data file cannot be deleted, reset it
            DataFileCache.deleteOrResetFreePos(database, dataFileName);

            try
            {
                EfzFileAccess fa = database.logger.getFileAccess();

                if (fa.IsStreamElement(backupFileName))
                {
                    FileArchiver.unarchive(backupFileName, dataFileName, fa,
                                           FileArchiver.COMPRESSION_ZIP);

                    return true;
                }

                return false;
            }
            catch (Exception t)
            {
                throw Error.error(t, ErrorCode.FILE_IO_ERROR,
                                  ErrorCode.M_Message_Pair, new Object[] {
                t.Message, backupFileName
            });
            }
#else
            throw new NotSupportedException();
#endif
        }

        /**
         * Restores in from an incremental backup
         */
        private bool restoreBackupIncremental()
        {

            try
            {
                if (fa.IsStreamElement(backupFileName))
                {
                    RAShadowFile.restoreFile(backupFileName, dataFileName);
                    deleteBackup();

                    return true;
                }

                return false;
            }
            catch (IOException e)
            {
                throw Error.error(ErrorCode.FILE_IO_ERROR, e);
            }
        }

        /**
         *  Parameter write indicates either an orderly close, or a fast close
         *  without backup.
         *
         *  When false, just closes the file.
         *
         *  When true, writes out all cached rows that have been modified and the
         *  free position pointer for the *.data file and then closes the file.
         */
        public virtual void close(bool write)
        {

            try
            {
                if (cacheReadonly)
                {
                    if (dataFile != null)
                    {
                        dataFile.close();

                        dataFile = null;
                    }

                    return;
                }

                StopWatch sw = new StopWatch();

                database.logger.logInfoEvent("DataFileCache.close(" + write
                                             + ") : start");

                if (write)
                {
                    cache.saveAll();
                    Error.printSystemOut("saveAll: " + sw.elapsedTime());
                    database.logger.logInfoEvent(
                        "DataFileCache.close() : save data");

                    if (fileModified || freeBlocks.isModified())
                    {

                        // set empty
                        dataFile.seek(LONG_EMPTY_SIZE);
                        dataFile.writeLong(freeBlocks.getLostBlocksSize());

                        // set end
                        dataFile.seek(LONG_FREE_POS_POS);
                        dataFile.writeLong(fileFreePosition);

                        // set saved flag;
                        dataFile.seek(FLAGS_POS);

                        int flags = dataFile.readInt();

                        flags = BitMap.set(flags, FLAG_ISSAVED);

                        dataFile.seek(FLAGS_POS);
                        dataFile.writeInt(flags);
                        database.logger.logInfoEvent(
                            "DataFileCache.close() : flags");

                        //
                        dataFile.seek(fileFreePosition);
                        database.logger.logInfoEvent(
                            "DataFileCache.close() : seek end");
                        Error.printSystemOut("pos and flags: " + sw.elapsedTime());
                    }
                }

                if (dataFile != null)
                {
                    dataFile.close();
                    database.logger.logInfoEvent("DataFileCache.close() : close");

                    dataFile = null;

                    Error.printSystemOut("close: " + sw.elapsedTime());
                }

                if (shadowFile != null)
                {
                    shadowFile.close();

                    shadowFile = null;
                }

                bool empty = fileFreePosition == INITIAL_FREE_POS;

                if (empty)
                {
                    fa.removeElement(dataFileName);
                    fa.removeElement(backupFileName);
                }
            }
            catch (Exception t)
            {
                database.logger.logSevereEvent("Close failed", t);

                throw Error.error(t, ErrorCode.FILE_IO_ERROR,
                                  ErrorCode.M_DataFileCache_close, new Object[] {
                t.Message, dataFileName
            });
            }
        }

        protected virtual void initBuffers()
        {

            if (rowOut == null
                    || rowOut.getOutputStream().getBuffer().Length
                       > initIOBufferSize)
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
                if (is180)
                {
                    rowOut = new RowOutputBinary180(256, cachedRowPadding);
                }
                else
                {
                    rowOut = new RowOutputBinaryEncode(database.logger.getCrypto(),
                                                       256, cachedRowPadding);
                }
#else
                rowOut = new RowOutputBinary180(256, cachedRowPadding);
#endif
            }
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            if (rowIn == null || rowIn.getBuffer().Length > initIOBufferSize)
            {
                if (is180)
                {
                    rowIn = new RowInputBinary180(new byte[256]);
                }
                else
                {
                    rowIn = new RowInputBinaryDecode(database.logger.getCrypto(),
                                                     new byte[256]);
                }
            }
#else
            rowOut = new RowOutputBinary180(256, cachedRowPadding);
#endif
        }

        public DataFileDefrag defrag()
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                cache.saveAll();

                DataFileDefrag dfd = new DataFileDefrag(database, this,
                    dataFileName);

                dfd.process();
                close(false);
                cache.clear();
                backupFile();
                database.schemaManager.setTempIndexRoots(dfd.getIndexRoots());
                database.logger.log.writeScript(false);
                database.getProperties().setDBModified(
                    EffiProzDatabaseProperties.FILES_NEW);
                database.logger.log.closeLog();
                database.logger.log.deleteLog();
                database.logger.log.renameNewScript();
                renameDataFile();
                renameBackupFile();
                database.getProperties().setDBModified(
                    EffiProzDatabaseProperties.FILES_NOT_MODIFIED);
                open(false);
                dfd.updateTransactionRowIDs();
                database.schemaManager.setIndexRoots(dfd.getIndexRoots());

                if (database.logger.log.dbLogWriter != null)
                {
                    database.logger.log.openLog();
                }

                database.getProperties().setDBModified(
                    EffiProzDatabaseProperties.FILES_MODIFIED);

                return dfd;
            }
            finally
            {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        /**
         * Used when a row is deleted as a result of some DML or DDL statement.
         * Removes the row from the cache data structures.
         * Adds the file space for the row to the list of free positions.
         */
        public virtual void remove(int i, PersistentStore store)
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif
            try
            {
                CachedObject r = release(i);

                if (r != null)
                {
                    int size = r.getStorageSize();

                    freeBlocks.add(i, size);
                }
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        public virtual void removePersistence(int i) { }

        /**
         * Allocates file space for the row. <p>
         *
         * Free space is requested from the block manager if it exists.
         * Otherwise the file is grown to accommodate it.
         */
        private int setFilePos(CachedObject r)
        {

            int rowSize = r.getStorageSize();
            int i = freeBlocks == null ? -1
                                             : freeBlocks.get(rowSize);

            if (i == -1)
            {
                i = (int)(fileFreePosition / cacheFileScale);

                long newFreePosition = fileFreePosition + rowSize;

                if (newFreePosition > maxDataFileSize)
                {
                    throw Error.error(ErrorCode.DATA_FILE_IS_FULL);
                }

                fileFreePosition = newFreePosition;
            }

            r.setPos(i);

            return i;
        }

        public virtual void add(CachedObject obj)
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                int i = setFilePos(obj);

                cache.put(i, obj);

                // was previously used for text tables
                if (storeOnInsert)
                {
                    saveRow(obj);
                }
            }
            finally
            {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        /**
         * For a CacheObject that had been previously released from the cache.
         * A new version is introduced, using the preallocated space for the object.
         */
        public void restore(CachedObject obj)
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                int i = obj.getPos();

                cache.put(i, obj);

                // was previously used for text tables
                if (storeOnInsert)
                {
                    saveRow(obj);
                }
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        public int getStorageSize(int i)
        {


#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterReadLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                CachedObject value = cache.get(i);

                if (value != null)
                {
                    return value.getStorageSize();
                }
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitReadLock();
#else
                Monitor.Exit(_lock);
#endif
            }

            return readSize(i);
        }

        public virtual CachedObject get(CachedObject obj, PersistentStore store,
                                bool keep)
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterReadLock();
#else
            Monitor.Enter(_lock);
#endif

            int pos;

            try
            {
                if (obj.isInMemory())
                {
                    if (keep)
                    {
                        obj.keepInMemory(true);
                    }

                    return obj;
                }

                pos = obj.getPos();

                if (pos < 0)
                {
                    return null;
                }

                obj = cache.get(pos);

                if (obj != null)
                {
                    if (keep)
                    {
                        obj.keepInMemory(true);
                    }

                    return obj;
                }
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitReadLock();
#else
                Monitor.Exit(_lock);
#endif
            }

            return getFromFile(pos, store, keep);
        }

        public virtual CachedObject get(int pos, PersistentStore store, bool keep)
        {

            CachedObject obj;

            if (pos < 0)
            {
                return null;
            }

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterReadLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                obj = cache.get(pos);

                if (obj != null)
                {
                    if (keep)
                    {
                        obj.keepInMemory(true);
                    }

                    return obj;
                }
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitReadLock();
#else
                Monitor.Exit(_lock);
#endif
            }

            return getFromFile(pos, store, keep);
        }

        private CachedObject getFromFile(int pos, PersistentStore store,
                                         bool keep) {

        CachedObject obj      = null;
        bool      outOfMemory = false;

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
        _lock.EnterWriteLock();
#else
        Monitor.Enter(_lock);
#endif


        try {
            obj = cache.get(pos);

            if (obj != null) {
                if (keep) {
                    obj.keepInMemory(true);
                }

                return obj;
            }

            for (int j = 0; j < 5; j++) {
                outOfMemory = false;

                try {
                    RowInputInterface rowInput = readObject(pos);

                    if (rowInput == null) {
                        return null;
                    }

                    obj = store.get(rowInput);

                    break;
                } catch (OutOfMemoryException err) {
                    cache.cleanUp();

                    outOfMemory = true;

                    database.logger.logSevereEvent("OOME in getFromFile", err);
                }
            }

            if (outOfMemory) {
                throw Error.error(ErrorCode.OUT_OF_MEMORY);
            }

            // for text tables with empty rows at the beginning,
            // pos may move forward in readObject
            pos = obj.getPos();

            cache.put(pos, obj);

            if (keep) {
                obj.keepInMemory(true);
            }

            store.set(obj);

            return obj;
        } catch (CoreException e) {
            database.logger.logSevereEvent(dataFileName + " getFromFile "
                                           + pos, e);

            throw e;
        } finally
        {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
            Monitor.Exit(_lock);
#endif
        }
    }

        RowInputInterface getRaw(int i)
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
        _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                return readObject(i);
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        protected int readSize(int pos)
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
        _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                dataFile.seek((long)pos * cacheFileScale);

                return dataFile.readInt();
            }
            catch (IOException e)
            {
                throw Error.error(ErrorCode.DATA_FILE_ERROR, e);
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        protected virtual RowInputInterface readObject(int pos)
        {

            try
            {
                dataFile.seek((long)pos * cacheFileScale);

                int size = dataFile.readInt();

                rowIn.resetRow(pos, size);
                dataFile.read(rowIn.getBuffer(), 4, size - 4);

                return rowIn;
            }
            catch (IOException e)
            {
                throw Error.error(ErrorCode.DATA_FILE_ERROR, e);
            }
        }

        public CachedObject release(int pos)
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
        _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                return cache.release(pos);
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        public virtual void saveRows(CachedObject[] rows, int offset, int count)
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
        _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                setFileModified();
                copyShadow(rows, offset, count);

                for (int i = offset; i < offset + count; i++)
                {
                    CachedObject r = rows[i];

                    saveRowNoLock(r);

                    rows[i] = null;
                }
            }
            catch (CoreException e)
            {
                database.logger.logSevereEvent("saveRows failed", e);

                throw e;
            }
            catch (Exception e)
            {
                database.logger.logSevereEvent("saveRows failed", e);

                throw Error.error(ErrorCode.DATA_FILE_ERROR, e);
            }
            finally
            {
                initBuffers();
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        /**
         * Writes out the specified Row. Will write only the Nodes or both Nodes
         * and table row data depending on what is not already persisted to disk.
         */
        public virtual void saveRow(CachedObject row)
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif
            try
            {
                setFileModified();
                saveRowNoLock(row);
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        public void saveRowNoLock(CachedObject row)
        {

            try
            {
                rowOut.reset();
                row.write(rowOut);
                dataFile.seek((long)row.getPos() * cacheFileScale);
                dataFile.write(rowOut.getOutputStream().getBuffer(), 0,
                               rowOut.getOutputStream().size());
            }
            catch (IOException e)
            {
                throw Error.error(ErrorCode.DATA_FILE_ERROR, e);
            }
        }

        protected void copyShadow(CachedObject[] rows, int offset,
                                  int count)
        {

            if (shadowFile != null)
            {
                for (int i = offset; i < offset + count; i++)
                {
                    CachedObject row = rows[i];
                    long seekpos = (long)row.getPos() * cacheFileScale;

                    shadowFile.copy(seekpos, row.getStorageSize());
                }

                shadowFile.close();
            }
        }

        /**
         *  Saves the *.data file as compressed *.backup.
         *
         * @throws  CoreException
         */
        public void backupFile()
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();


            try
            {
                if (database.logger.propIncrementBackup)
                {
                    if (fa.IsStreamElement(backupFileName))
                    {
                        fa.removeElement(backupFileName);
                    }

                    return;
                }

                if (fa.IsStreamElement(dataFileName))
                {
                    FileArchiver.archive(dataFileName, backupFileName + ".new",
                                         database.logger.getFileAccess(),
                                         FileArchiver.COMPRESSION_ZIP);
                }
            }
            catch (IOException e)
            {
                database.logger.logSevereEvent("backupFile failed", e);

                throw Error.error(ErrorCode.DATA_FILE_ERROR, e);
            }
            finally
            {


            _lock.ExitWriteLock();
            }
#else
            throw new NotSupportedException();
#endif
            
        }

        void renameBackupFile()
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                if (database.logger.propIncrementBackup)
                {
                    if (fa.IsStreamElement(backupFileName))
                    {
                        fa.removeElement(backupFileName);
                    }

                    return;
                }

                if (fa.IsStreamElement(backupFileName + ".new"))
                {
                    fa.removeElement(backupFileName);
                    fa.RenameElement(backupFileName + ".new", backupFileName);
                }
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        /**
         *  Renames the *.data.new file.
         *
         * @throws  CoreException
         */
        void renameDataFile()
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                if (fa.IsStreamElement(dataFileName + ".new"))
                {
                    deleteFile();
                    fa.RenameElement(dataFileName + ".new", dataFileName);
                }
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        void deleteFile()
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {

                // first attemp to delete
                fa.removeElement(dataFileName);

                // OOo related code
                if (database.logger.isStoredFileAccess())
                {
                    return;
                }

                // OOo end
                if (fa.IsStreamElement(dataFileName))
                {
                    fa.removeElement(dataFileName);

                    if (fa.IsStreamElement(dataFileName))
                    {

                        //#ifdef JAVA2FULL
                        try
                        {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK

                            FileInfo file = new FileInfo(database.getCanonicalPath());
                            FileInfo[] list = file.Directory.GetFiles();

                            for (int i = 0; i < list.Length; i++)
                            {
                                if (list[i].Name.EndsWith(".old")
                                        && list[i].Name.StartsWith(
                                            file.Name))
                                {
                                    list[i].Delete();
                                }
                            }
#else
                            string canonicalPath =   database.getCanonicalPath();
                         string parentDir = FileUtil.GetDefaultInstance().getParentDir(canonicalPath);

                         string[] fileNames = FileUtil.GetDefaultInstance().getAllFiles(parentDir);
                         for (int i = 0; i < fileNames.Length; i++)
                         {
                             if (fileNames[i].EndsWith(".old")
                                        && fileNames[i].StartsWith(
                                            canonicalPath))
                             {
                                 FileUtil.GetDefaultInstance().removeElement(fileNames[i]);
                             }
                         }
#endif
                        }
                        catch (Exception) { }


                        //#endif JAVA2FULL
                        String oldName = dataFileName + ".old";

                        fa.removeElement(oldName);

                        if (fa.IsStreamElement(oldName))
                        {
                            String timestamp = StringUtil.toPaddedString(String.Format("{0:x}", DateTime.Now.Ticks)
                                , 8, '0',
                                        true);
                            String discardName = dataFileName + "." + timestamp
                                                 + ".old";

                            fa.RenameElement(oldName, discardName);

                           
                            FileUtil.GetDefaultInstance().deleteOnExit(discardName);
                        }

                        fa.RenameElement(dataFileName, oldName);

                        FileUtil.GetDefaultInstance().deleteOnExit(oldName);
                    }
                }
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        void deleteBackup()
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif

            try
            {
                fa.removeElement(backupFileName);
            }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        /**
         * This method deletes a data file or resets its free position.
         * this is used only for nio files - not OOo files
         */
        public static void deleteOrResetFreePos(Database database, String filename)
        {

            ScaledRAFile raFile = null;

            database.logger.getFileAccess().removeElement(filename);

            // OOo related code
            if (database.logger.isStoredFileAccess())
            {
                return;
            }

            // OOo end
            if (!database.logger.getFileAccess().IsStreamElement(filename))
            {
                return;
            }

            try
            {
                raFile = new ScaledRAFile(database, filename, false);

                raFile.seek(LONG_FREE_POS_POS);
                raFile.writeLong(INITIAL_FREE_POS);
            }
            catch (IOException e)
            {
                database.logger.logSevereEvent("deleteOrResetFreePos failed", e);
            }
            finally
            {
                if (raFile != null)
                {
                    try
                    {
                        raFile.close();
                    }
                    catch (IOException e)
                    {
                        database.logger.logWarningEvent("Failed to close RA file",
                                                        e);
                    }
                }
            }
        }

        public int capacity()
        {
            return maxCacheRows;
        }

        public long bytesCapacity()
        {
            return maxCacheBytes;
        }

        public long getTotalCachedBlockSize()
        {
            return cache.getTotalCachedBlockSize();
        }

        public int getFreeBlockCount()
        {
            return freeBlocks.size();
        }

        public int getTotalFreeBlockSize()
        {
            return 0;
        }

        public long getFileFreePos()
        {
            return fileFreePosition;
        }

        public int getCachedObjectCount()
        {
            return cache.size();
        }

        public int getAccessCount()
        {
            return cache.incrementAccessCount();
        }

        public String getFileName()
        {
            return dataFileName;
        }

        public bool hasRowInfo()
        {
            return _hasRowInfo;
        }

        public bool isFileModified()
        {
            return fileModified;
        }

        public bool isFileOpen()
        {
            return dataFile != null;
        }

        protected virtual void setFileModified()
        {

#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.EnterWriteLock();
#else
            Monitor.Enter(_lock);
#endif
            try
            {
                if (!fileModified)
                {

                    // unset saved flag;
                    dataFile.seek(FLAGS_POS);

                    int flags = dataFile.readInt();

                    flags = BitMap.unset(flags, FLAG_ISSAVED);

                    dataFile.seek(FLAGS_POS);
                    dataFile.writeInt(flags);
                    dataFile.synch();

                    fileModified = true;
                }
            }
            catch (Exception ) { }
            finally
            {
#if !SILVERLIGHT && !PLATFORM_COMPACTFRAMEWORK
            _lock.ExitWriteLock();
#else
                Monitor.Exit(_lock);
#endif
            }
        }

        public bool isDataReadOnly()
        {
            return this.cacheReadonly;
        }

    }
}
